package event

import (
	"fmt"
	"log"
	"reflect"
	"sync"
)

type Manager struct {
	mutex sync.Mutex
	events map[Event]map[uintptr]interface{}

	LogError func(format string, args ...interface{})
}

func NewManager() *Manager {
	result := new(Manager)
	result.events = make(map[Event]map[uintptr]interface{})

	return result
}

func (manager *Manager) Listen(event Event, callback interface{}) {
	if event == nil {
		panic("event is nil")
		return
	}

	cbType := reflect.TypeOf(callback)
	if cbType.Kind() != reflect.Func {
		panic("callback is not function")
		return
	}

	cbValue := reflect.ValueOf(callback)
	if cbValue.IsNil() {
		panic("callback is nil")
		return
	}

	if err := event.Verify(callback); err != nil {
		panic(err)
		return
	}

	manager.mutex.Lock()
	defer manager.mutex.Unlock()

	events, exists := manager.events[event]
	if !exists {
		events = make(map[uintptr]interface{})
		manager.events[event] = events
	}

	pointer := cbValue.Pointer()
	events[pointer] = callback

	return
}

// goroutine safe
func (manager *Manager) Notify(event Event, args ...interface{}) {
	if event == nil {
		return
	}

	logError := manager.LogError
	if logError == nil {
		logError = log.Printf
	}

	callbacks := manager.events[event]
	for _, callback := range callbacks {
		if err := onTrigger(event, callback, args); err != nil {
			logError("trigger event %s failed, %v", reflect.TypeOf(event).String(), err)
		}
	}

	return
}

func onTrigger(event Event, callback interface{}, args []interface{}) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v", r)
		}
	}()

	err = event.Call(callback, args)
	return
}
